forked from
leaflet.pub/leaflet
a tool for shared writing and social publishing
1import { NextResponse } from "next/server";
2import { DidResolver } from "@atproto/identity";
3import { parseReqNsid, verifyJwt } from "@atproto/xrpc-server";
4import { supabaseServerClient } from "supabase/serverClient";
5import { PubLeafletDocument } from "lexicons/api";
6
7const serviceDid = "did:web:leaflet.pub:lish:feeds";
8export async function GET(
9 req: Request,
10 { params }: { params: Promise<{ path: string[] }> },
11) {
12 let { path } = await params;
13 if (path[0] === "did.json")
14 return NextResponse.json({
15 "@context": ["https://www.w3.org/ns/did/v1"],
16 id: serviceDid,
17 service: [
18 {
19 id: "#bsky_fg",
20 type: "BskyFeedGenerator",
21 serviceEndpoint: `https://leaflet.pub/lish/feeds`,
22 },
23 ],
24 });
25 let auth = validateAuth(req, serviceDid);
26 if (!auth) return NextResponse.json({}, { status: 301 });
27 let { data: publications } = await supabaseServerClient
28 .from("publication_subscriptions")
29 .select(`publications(documents_in_publications(documents(*)))`)
30 .eq("identity", auth);
31 return NextResponse.json({
32 feed: [
33 ...(publications || []).flatMap((pub) => {
34 let posts = pub.publications?.documents_in_publications || [];
35 return posts.flatMap((p) => {
36 if (!p.documents?.data) return [];
37 let record = p.documents.data as PubLeafletDocument.Record;
38 if (!record.postRef) return [];
39 return { post: record.postRef.uri };
40 });
41 }),
42 ],
43 });
44}
45
46const didResolver = new DidResolver({});
47const validateAuth = async (
48 req: Request,
49 serviceDid: string,
50): Promise<string | null> => {
51 const authorization = req.headers.get("authorization");
52 if (!authorization?.startsWith("Bearer ")) {
53 return null;
54 }
55 const jwt = authorization.replace("Bearer ", "").trim();
56 const nsid = parseReqNsid(req);
57 const parsed = await verifyJwt(jwt, serviceDid, nsid, async (did: string) => {
58 return didResolver.resolveAtprotoKey(did);
59 });
60 return parsed.iss;
61};